use std::fmt::{Debug, Formatter};
use std::path::Path;
use std::sync::Arc;
use mf_core::{Event, EventHandler, ForgeError, ForgeResult};
use mf_model::NodePool;
use mf_search::{IndexEvent, IndexService, RebuildScope, SqliteBackend};
use mf_search::step_registry::ensure_default_step_indexers;
use mf_state::Transaction;
use mf_transform::step::Step;

pub struct SearchIndexHandler{
    service: Arc<IndexService>,
}

impl Debug for SearchIndexHandler {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "SearchIndexHandler service")
    }
}

#[async_trait::async_trait]
impl EventHandler<Event> for  SearchIndexHandler{
    async fn handle(&self, event: &Event) -> ForgeResult<()> {
        match  event {
            Event::Create(state_arc) => {
                let service_ref = self.service.clone();
                let node_pool_ref = state_arc.node_pool.clone();
                tokio::spawn(async move {
                    let _ = service_ref
                        .handle(IndexEvent::Rebuild {
                            pool: node_pool_ref,
                            scope: RebuildScope::Full,
                        })
                        .await;
                });
            }
            Event::TrApply { old_state,new_state,transactions } => {
                let steps = collect_steps(transactions, StepDirection::Forward);
                let pool_before = old_state.doc();
                let pool_after = new_state.doc();
                self.spawn_index_event(Some(pool_before), pool_after, steps);
            }
            Event::Undo { old_state, new_state, transactions } => {
                let steps = collect_steps(transactions, StepDirection::Backward);
                let pool_before = old_state.doc();
                let pool_after = new_state.doc();
                self.spawn_index_event(Some(pool_before), pool_after, steps);
            }
            Event::Redo { old_state, new_state, transactions } => {
                let steps = collect_steps(transactions, StepDirection::Forward);
                let pool_before = old_state.doc();
                let pool_after = new_state.doc();
                self.spawn_index_event(Some(pool_before), pool_after, steps);
            }
            Event::Jump { old_state, new_state, transactions, steps } => {
                if *steps == 0 {
                    return Ok(());
                }
                let mode = if *steps < 0 {
                    StepDirection::Backward
                } else {
                    StepDirection::Forward
                };
                let collected = collect_steps(transactions, mode);
                let pool_before = old_state.doc();
                let pool_after = new_state.doc();
                self.spawn_index_event(Some(pool_before), pool_after, collected);
            }
            Event::TrFailed { .. } => {}
            Event::HistoryCleared => {}
            _ => {}
        }
        Ok(())
    }
}

impl SearchIndexHandler {
    fn spawn_index_event(
        &self,
        pool_before: Option<Arc<NodePool>>,
        pool_after: Arc<NodePool>,
        steps: Vec<Arc<dyn Step>>,
    ) {
        let svc = self.service.clone();
        if steps.is_empty() {
            tokio::spawn(async move {
                let _ = svc
                    .handle(IndexEvent::Rebuild {
                        pool: pool_after,
                        scope: RebuildScope::Full,
                    })
                    .await;
            });
        } else {
            tokio::spawn(async move {
                let _ = svc
                    .handle(IndexEvent::TransactionCommitted {
                        pool_before,
                        pool_after,
                        steps,
                    })
                    .await;
            });
        }
    }
}

enum StepDirection {
    Forward,
    Backward,
}

fn collect_steps(
    transactions: &[Arc<Transaction>],
    mode: StepDirection,
) -> Vec<Arc<dyn Step>> {
    match mode {
        StepDirection::Forward => transactions
            .iter()
            .flat_map(|tr| tr.steps.iter().cloned())
            .collect(),
        StepDirection::Backward => {
            let mut out = Vec::new();
            for tr in transactions.iter().rev() {
                let mut inverses: Vec<Arc<dyn Step>> =
                    tr.invert_steps.iter().cloned().collect();
                inverses.reverse();
                out.extend(inverses);
            }
            out
        },
    }
}

pub async fn create_search_index_handler(
    temp_root: &Path
) -> ForgeResult<Arc<SearchIndexHandler>> {
    ensure_default_step_indexers();
    tokio::fs::create_dir_all(temp_root).await.map_err(|e|ForgeError::Internal{ message: e.to_string(), location: None })?;
    let backend = Arc::new(SqliteBackend::new_in_temp_root(temp_root).await?);
    let service = Arc::new(IndexService::new(backend));
    Ok(Arc::new(SearchIndexHandler { service }))
}
